/**
 * Project: d20Campaigner
 * Created: Sep 4, 2007 by bebopJMM
 */
package org.rollinitiative.d20;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The Adjustment captures a singular adjustment to a modifiable score (AdjustableValue), such as an
 * Ability, Skill, or Save.
 * 
 * @author bebopJMM
 */
public class Adjustment
{

   static final Log LOG = LogFactory.getLog(Adjustment.class);

   public enum AdjustmentSource {
      INHERENT, EQUIPMENT, EFFECT
   };

   /**
    * Listeners to changes in the Adjustment
    */
   List<AdjustmentListener> subscribers;

   /**
    * Value of the adjustment, which can change over time
    */
   int value;

   /**
    * Limit of the adjustment
    */
   int limit = Integer.MAX_VALUE;

   final AdjustmentSource type;

   final QName pedigree;


   /**
    * Instantiates a new Adjustment with an empty list of subscribers
    * 
    * @param type AdjustmentType for the new Adjustment
    * @param value initial value for the new Adjustment
    * @param pedigree source of this new Adjustment
    */
   public Adjustment(AdjustmentSource type, int value, QName pedigree)
   {
      this.value = value;
      this.type = type;
      this.pedigree = pedigree;
      subscribers = new ArrayList<AdjustmentListener>();
   }


   /**
    * @return the current value of the Adjustment
    */
   public int getValue()
   {
      if (value <= limit)
         return value;
      else
         return limit;
   }


   /**
    * This method updates the value of this Adjustment and notifies all subscribers of the change.
    * 
    * @param value new value for the adjustment
    */
   public void setValue(int value)
   {
      this.value = value;
      notifyOfChange();
   }


   /**
    * @return the current limit being applied to the adjustment. Integer.MAX_VALUE is essentially no
    *         limit.
    */
   public int getLimit()
   {
      return limit;
   }


   /**
    * This method updates the limit of this Adjustment and notifies all subscribers of the change.
    * 
    * @param limit new limit to be applied
    */
   public void setLimit(int limit)
   {
      this.limit = limit;
      notifyOfChange();
   }


   /**
    * @return the AdjustementType of the Adjustment
    */
   public AdjustmentSource getType()
   {
      return type;
   }


   /**
    * @return the pedigree of the Adjustment
    */
   public QName getPedigree()
   {
      return pedigree;
   }


   /**
    * Add the listener as a subscriber to changes in the adjustment if it hasn't already subscribed.
    * 
    * @param listener AdjustmentListener to add.
    */
   public synchronized void subscribe(AdjustmentListener listener)
   {
      if (!subscribers.contains(listener)) {
         subscribers.add(listener);
      }
   }


   /**
    * Removes the listener as a subscriber to changes in the adjustment
    * 
    * @param listener AdjustmentListener to remove
    */
   public synchronized void unsubscribe(AdjustmentListener listener)
   {
      if (subscribers.contains(listener)) {
         subscribers.remove(listener);
      }
   }


   /**
    * This method notifies all AdjustmentListener subscribers of a change in the Adjustment's value
    * via the subcriber's valueChanged() method.
    * 
    * @see org.rollinitiative.d20.AdjustmentListener#valueChanged(org.rollinitiative.d20.Adjustment)
    */
   protected void notifyOfChange()
   {
      synchronized (subscribers) {
         if (LOG.isDebugEnabled()) {
            LOG.debug("Notifying subscribers, total = " + subscribers.size());
         }
         for (AdjustmentListener listener : subscribers) {
            listener.valueChanged(this);
         }
      }
   }

}
